Skip to main content

Upgrade Version: Run with K8s

This guide shows how to deploy applications from Jenkins directly to Kubernetes using kubectl apply.


Architecture Overview

alt text Components:

  • GCP VM with Jenkins + GitLab + Nginx (Docker containers)
  • GKE Kubernetes Cluster (separate)
  • GitLab Registry (private Docker images)
  • kubectl in Jenkins to deploy to K8s

Prerequisites

You need:

  • GCP VM with Jenkins + GitLab + Nginx running (from previous guide)
  • GKE Kubernetes cluster already created
  • kubectl installed and configured to access GKE
  • kubeconfig credentials ready

Step 1: Create Kubernetes Secret for GitLab Registry

Your K8s cluster needs credentials to pull images from GitLab Registry.

1.1 Create the Secret in K8s

kubectl create secret docker-registry gitlab-registry-secret \
--docker-server=<VM_EXTERNAL_IP>:5380 \
--docker-username=root \
--docker-password=<GITLAB_ROOT_PASSWORD> \
-n default

Example:

kubectl create secret docker-registry gitlab-registry-secret \
--docker-server=34.136.104.61:5380 \
--docker-username=root \
--docker-password=MyGitLabPassword123 \
-n default

1.2 Verify the Secret

kubectl get secret gitlab-registry-secret -n default
kubectl describe secret gitlab-registry-secret -n default

Step 2: Install kubectl in Jenkins Container

Jenkins needs kubectl to deploy to K8s.

2.1 Update docker-compose.yml

Modify the Jenkins service to install kubectl:

jenkins:
image: jenkins/jenkins:lts
container_name: jenkins
ports:
- "8080:8080"
- "50000:50000"
volumes:
- jenkins_data:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
- ~/.kube/config:/root/.kube/config:ro # Add this line
environment:
- JENKINS_OPTS="--prefix=/jenkins"
user: root
restart: always
networks:
- devops-network

2.2 Rebuild Jenkins Container

docker-compose down
docker-compose up -d jenkins

2.3 Install kubectl in Jenkins

SSH into the Jenkins container and install kubectl:

docker exec -it jenkins bash

# Inside container:
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/local/bin/

# Verify
kubectl version --client

Or use a Dockerfile to build a custom Jenkins image with kubectl pre-installed.


Step 3: Create Kubernetes Deployment Manifest

Create a deployment.yaml file in your GitLab repo:

apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
imagePullSecrets:
- name: gitlab-registry-secret # Reference the K8s secret
containers:
- name: demo-app
image: <VM_EXTERNAL_IP>:5380/root/demo-app:latest
imagePullPolicy: Always
ports:
- containerPort: 3000
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 10
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: demo-app-svc
namespace: default
spec:
type: LoadBalancer
selector:
app: demo-app
ports:
- port: 80
targetPort: 3000
protocol: TCP

Key points:

  • imagePullSecrets: - name: gitlab-registry-secret → tells K8s to use the secret
  • image: <VM_EXTERNAL_IP>:5380/root/demo-app:latest → pull from GitLab Registry
  • imagePullPolicy: Always → always pull fresh image on deployment
  • Service exposes the app via LoadBalancer

Commit this to your GitLab repo.


Step 4: Update Jenkins Pipeline to Deploy to K8s

Modify your Jenkins Jenkinsfile to include kubectl deployment:

pipeline {
agent any

environment {
GITLAB_REGISTRY = '34.136.104.61:5380' // Change to your VM IP
PROJECT_NAME = 'root' // GitLab group/user
IMAGE_NAME = "${GITLAB_REGISTRY}/${PROJECT_NAME}/demo-app"
IMAGE_TAG = "${BUILD_NUMBER}"
KUBECONFIG = '/root/.kube/config' // K8s config path
KUBE_NAMESPACE = 'default'
}

stages {
stage('Checkout') {
steps {
echo "Cloning repository..."
git credentialsId: 'gitlab-token',
url: 'http://gitlab/root/demo-app.git',
branch: 'main'
}
}

stage('Build Docker Image') {
steps {
echo "Building Docker image..."
script {
sh "docker build -t ${IMAGE_NAME}:${IMAGE_TAG} ."
sh "docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest"
}
}
}

stage('Push to GitLab Registry') {
steps {
echo "Pushing to GitLab Registry..."
script {
withCredentials([usernamePassword(
credentialsId: 'gitlab-credentials',
usernameVariable: 'USER',
passwordVariable: 'PASS'
)]) {
sh '''
echo $PASS | docker login -u $USER --password-stdin ${GITLAB_REGISTRY}
docker push ${IMAGE_NAME}:${IMAGE_TAG}
docker push ${IMAGE_NAME}:latest
'''
}
}
}
}

stage('Update Deployment Manifest') {
steps {
echo "Updating deployment image tag..."
script {
sh '''
sed -i "s|image: .*/demo-app:.*|image: ${IMAGE_NAME}:${IMAGE_TAG}|g" deployment.yaml
cat deployment.yaml
'''
}
}
}

stage('Deploy to Kubernetes') {
steps {
echo "Deploying to Kubernetes..."
script {
sh '''
kubectl apply -f deployment.yaml --kubeconfig=${KUBECONFIG}
kubectl rollout status deployment/demo-app -n ${KUBE_NAMESPACE} --timeout=2m
'''
}
}
}

stage('Verify Deployment') {
steps {
echo "Verifying deployment..."
script {
sh '''
echo "Pods:"
kubectl get pods -n ${KUBE_NAMESPACE} -l app=demo-app
echo ""
echo "Service:"
kubectl get svc -n ${KUBE_NAMESPACE} demo-app-svc
'''
}
}
}
}

post {
success {
echo "✅ Deployment to Kubernetes successful!"
sh "kubectl get all -n ${KUBE_NAMESPACE} -l app=demo-app"
}
failure {
echo "❌ Deployment failed!"
sh "kubectl describe deployment demo-app -n ${KUBE_NAMESPACE}"
}
}
}

Pipeline stages:

  1. Checkout → clone app code from GitLab
  2. Build Docker Image → build and tag image
  3. Push to GitLab Registry → push image with build number tag and latest tag
  4. Update Deployment Manifest → update image tag in deployment.yaml
  5. Deploy to Kubernetes → run kubectl apply -f deployment.yaml
  6. Verify Deployment → check pods and services

Step 5: Add Jenkins Credentials for GitLab Registry

Jenkins needs credentials to push images to GitLab Registry.

5.1 Add Credentials in Jenkins UI

  1. Go to Manage Jenkins → Manage Credentials
  2. Click Add Credentials
  3. Fill in:
    • Kind: Username with password
    • Username: root (GitLab admin user)
    • Password: <GITLAB_ROOT_PASSWORD>
    • ID: gitlab-credentials
  4. Click Create

Step 6: Set Up GitLab Webhook to Trigger Jenkins

6.1 Create Jenkins Pipeline Job

  1. In Jenkins, click New Item
  2. Name: demo-app-pipeline
  3. Type: Pipeline
  4. In Build Triggers, check Build when a change is pushed to GitLab
  5. In Pipeline Script, paste the Jenkinsfile from Step 4
  6. Save

6.2 Add Webhook in GitLab

  1. In GitLab project, go to Settings → Webhooks
  2. URL: http://jenkins:8080/project/demo-app-pipeline
  3. Secret Token: (optional, leave blank for testing)
  4. Trigger: Check Push events
  5. Uncheck Enable SSL verification (for testing)
  6. Click Add webhook
  7. Click Test → Push events

Jenkins should automatically start a build.


Step 7: Test the Complete Pipeline

7.1 Clone App Repo and Make Changes

git clone http://<VM_EXTERNAL_IP>:8081/root/demo-app.git
cd demo-app

7.2 Add Dockerfile

Create a Dockerfile:

FROM node:16-alpine

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

(Or use any language/framework you prefer)

7.3 Commit and Push

git add Dockerfile deployment.yaml
git commit -m "Add Docker and K8s deployment"
git push origin main

7.4 Watch Jenkins Build and Deploy

  1. Jenkins receives webhook notification
  2. Jenkins builds Docker image
  3. Jenkins pushes to GitLab Registry
  4. Jenkins runs kubectl apply -f deployment.yaml
  5. K8s pulls image from GitLab Registry
  6. Pods start running
  7. Service exposes app via LoadBalancer

Check in K8s:

kubectl get pods -n default -l app=demo-app
kubectl get svc -n default demo-app-svc
kubectl logs -n default deployment/demo-app

Get external IP:

kubectl get svc demo-app-svc -n default
# Copy the EXTERNAL-IP and open in browser

Updating Your App

Every time you push code:

  1. Git push → GitLab webhook fires
  2. Jenkins builds new Docker image with BUILD_NUMBER tag
  3. Jenkins pushes to GitLab Registry
  4. Jenkins updates deployment.yaml with new image tag
  5. Jenkins runs kubectl apply -f deployment.yaml
  6. K8s sees new image tag
  7. K8s rolls out new pods automatically
  8. Old pods gradually replaced (rolling update)

No manual kubectl commands needed!


Troubleshooting

Jenkins can't access GitLab Registry

Error: denied: access denied

Fix:

# Verify credentials in Jenkins
docker exec jenkins docker login 34.136.104.61:5380
# Enter username: root, password: <GitLab password>

# Should see "Login Succeeded"

K8s can't pull image from GitLab Registry

Error: ImagePullBackOff or Failed to pull image

Fix:

# Verify secret exists
kubectl get secret gitlab-registry-secret -n default

# Verify secret contents
kubectl get secret gitlab-registry-secret -n default -o yaml

# Check if image path is correct
kubectl describe pod <pod-name> -n default
# Look for image pull errors

# Test manual pull
docker pull 34.136.104.61:5380/root/demo-app:latest

Deployment times out

Error: deployment rollout status timeout

Fix:

# Check pod status
kubectl describe pod <pod-name> -n default

# Check image pull status
kubectl get events -n default --sort-by='.lastTimestamp'

# Check resource limits
kubectl top nodes
kubectl top pods -n default

# Increase timeout in Jenkinsfile if needed
# kubectl rollout status deployment/demo-app -n ${KUBE_NAMESPACE} --timeout=5m

Jenkins doesn't have kubectl access

Error: kubectl command not found or kubeconfig not found

Fix:

# SSH into Jenkins container
docker exec -it jenkins bash

# Check if kubectl is installed
which kubectl

# Check if kubeconfig exists
ls -la ~/.kube/config

# If not, install kubectl again (see Step 2)

Image not updating in K8s

Problem: Even after pushing new image, pods still run old image

Fix:

# Set imagePullPolicy to Always in deployment.yaml
imagePullPolicy: Always

# Or manually delete old pods
kubectl delete pod -l app=demo-app -n default

# Or force new rollout
kubectl rollout restart deployment/demo-app -n default

Security Notes

  1. GitLab Registry Secret should be created in each namespace that needs access
  2. KUBECONFIG mounted as read-only in Jenkins container
  3. Jenkins service account should have limited RBAC permissions (not cluster-admin)
  4. Credentials stored in Jenkins credentials store, never hardcoded
  5. Network policies can restrict traffic between namespaces/pods

Next Steps

To improve this setup:

  1. Add RBAC → Create Jenkins service account with minimal permissions
  2. Add network policies → Restrict traffic between pods
  3. Add monitoring → Prometheus + Grafana to monitor deployments
  4. Add logging → ELK stack or Google Cloud Logging
  5. Add backup → Backup K8s configurations and GitLab data
  6. Add multiple environments → Dev, staging, prod clusters with separate pipelines

Conclusion

You now have a simple, working CI/CD pipeline:

  • Jenkins builds images and deploys
  • GitLab Registry stores images
  • Kubernetes runs the app